package hit.core;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;

public class ItemFacade extends Observable{
	private ItemIndex itemIndex;
	
	/**
	 * Default constructor.
	 */
	public ItemFacade() { 
		itemIndex = HomeInventoryTracker.Instance().GetItemIndex();
	}
	
	/**
	 * Construct the ItemFacade from a specified ItemIndex
	 * @param itemIndex the Item Index to use
	 * */
	public ItemFacade(ItemIndex itemIndex) {
		this.itemIndex = itemIndex;
	}
	
	/**
	 * Returns an Item by its barcode or returns null
	 * @param b the barcode to search for
	 * @return the Item searched for or null if it's not in the index
	 * */
	public Item GetItemByBarcode(String b) {
		Barcode barcode;
		
		//if we get an IllegalArgumentException, the barcode was invalid
		try {
			barcode = new Barcode(b);
		}
		catch (IllegalArgumentException e) {
			return null;
		}
		
		//if the barcode wasn't valid, we already know we won't find the item
		if (!Barcode.IsValid(barcode))
			return null;
		
		//return the item from the itemindex
		return itemIndex.GetItemByBarcode(barcode);
	}
	
	/**
	 * Edits an item currently in the HIT.
	 * @param before An {@link Item} in the HIT.
	 * @param after The altered item.
	 * @return the edited Item
	 * @throws IllegalArgumentException
	 */
	public Item EditItem(Item before, Item after) throws IllegalArgumentException{
		if (!itemIndex.CanUpdateItem(before, after))
			throw new IllegalArgumentException("Item cannot be updated");
		itemIndex.UpdateItem(before, after);
		
		//create a notification for the observers
		List<Item> modifiedItems = new ArrayList<Item>();
		modifiedItems.add(after);
		ItemNotification notification = new ItemNotification(
							ItemNotification.Type.EDIT_ITEM, modifiedItems);
		
		this.setChanged();
		notifyObservers(notification);
		
		return after;
	}
	
	/**
	 * Adds [count] instances of [product] as new {@link Item}s
	 * @param product The {@link Product} that describes the items being created.
	 * @param count The number of items to instantiate and store.
	 * @param parent the ProductContainer to add them to
	 * @return the List of Items added
	 * @throws IllegalArgumentException
	 */
	public List<Item> AddItems(Product product, int count, Date entryDate, StorageUnit unit) 
															throws IllegalArgumentException{
		//first check that the product was a legal product
		if (!Product.IsValid(product)) {
			throw new IllegalArgumentException("Illegal product for AddItems");
		}
		
		ProductIndex productIndex = HomeInventoryTracker.Instance().GetProductIndex();
		ProductContainer parent = productIndex.FindProductContainer(unit, product);
		
		if (parent == null)
			parent = unit;
		
		List<Item> addedItems = new ArrayList<Item>();
		
		//loop and add all the items that should be added
		for (int i = 0; i < count; i++) {
			Item item = product.CreateItem(parent, entryDate);
			
			if (itemIndex.CanAddItem(item)) {
				itemIndex.AddItem(item);
				addedItems.add(item);
			}
			else {
				throw new IllegalArgumentException("Cannot add that Item");
			}
		}
		
		//add the productContainer to the product
		product.AddProductContainer(parent);
		
		//create a notification for the observers
		ItemNotification notification = new ItemNotification(ItemNotification.Type.ADD_ITEMS,
															addedItems);
		this.setChanged();
		notifyObservers(notification);
		
		return addedItems;
	}
	
	/**
	 * Add a List of Items to the HIT
	 * @param items the List of Items to add
	 * @throws IllegalArgumentException thrown if items is null or there is an invalid Item
	 * in the List
	 * */
	public void AddItems(List<Item> items) throws IllegalArgumentException {
		if (items == null) {
			throw new IllegalArgumentException("List of Items is null");
		}
		
		ProductIndex productIndex = HomeInventoryTracker.Instance().GetProductIndex();
		
		//loop through each item and add it to the HIT
		for (Item item : items) {
			//first find which container to add the Item to
			ProductContainer parent = productIndex.FindProductContainer(item.GetContainer(), 
																		item.GetProduct());
			if (parent == null)
				parent = item.GetContainer();
			
			//add the item
			if (itemIndex.CanAddItem(item)) {
				itemIndex.AddItem(item);
			}
			else {
				throw new IllegalArgumentException("Cannot add that Item");
			}
			
			//add the productContainer to the product
			item.GetProduct().AddProductContainer(parent);
		}
		
		//create a notification for the observers
		ItemNotification notification = new ItemNotification(ItemNotification.Type.ADD_ITEMS,
															items);
		this.setChanged();
		notifyObservers(notification);
	}
	
	/**
	 * Undo removing a List of Items from the HIT
	 * @param items the List of Items to add
	 * @throws IllegalArgumentException thrown if items is null or there is an invalid Item
	 * in the List
	 * */
	public void UndoRemoveItems(List<Item> items) throws IllegalArgumentException {
		if (items == null) {
			throw new IllegalArgumentException("List of Items is null");
		}
		
		ProductIndex productIndex = HomeInventoryTracker.Instance().GetProductIndex();
		
		//loop through each item and add it to the HIT
		for (Item item : items) {
			//first find which container to add the Item to
			ProductContainer parent = productIndex.FindProductContainer(item.GetContainer(), 
																		item.GetProduct());
			if (parent == null)
				parent = item.GetContainer();
			
			//add the item
			if (itemIndex.CanAddItem(item)) {
				itemIndex.UndoRemoveItem(item);
			}
			else {
				throw new IllegalArgumentException("Cannot add that Item");
			}
			
			//add the productContainer to the product
			item.GetProduct().AddProductContainer(parent);
		}
		
		//create a notification for the observers
		ItemNotification notification = new ItemNotification(ItemNotification.Type.ADD_ITEMS,
															items);
		this.setChanged();
		notifyObservers(notification);
	}
			
	/**
	 * Removes an item from the HIT.
	 * @param item The {@link Item} to remove.
	 * @return the removed Item
	 * @throws IllegalArgumentException
	 */
	public Item RemoveItem(Item item) throws IllegalArgumentException{
		
		return RemoveItem(item, new Date());
	}
	
	public Item RemoveItem(Item item, Date exitDate) throws IllegalArgumentException{
		if (!itemIndex.CanRemoveItem(item))
			throw new IllegalArgumentException("Item cannot be removed");
		itemIndex.RemoveItem(item, exitDate);
		
		//create a notification for the observers
		List<Item> RemovedItem = new ArrayList<Item>();
		RemovedItem.add(item);
		ItemNotification notification = new ItemNotification(
							ItemNotification.Type.REMOVE_ITEM, RemovedItem);
		
		this.setChanged();
		notifyObservers(notification);
		
		return item;
	}
	
	/**
	 * Remove a List of Items from the HIT
	 * @param items the List of Items to remove
	 * @throws IllegalArgumentException thrown if any of the Items could not be removed
	 * */
	public void RemoveItems(List<Item> items) {
		for (Item item: items) {
			if (!itemIndex.CanRemoveItem(item))
				throw new IllegalArgumentException("Item cannot be removed");
			itemIndex.RemoveItem(item, new Date());
		}
		
		//notify the observers
		ItemNotification notification = new ItemNotification(
				ItemNotification.Type.REMOVE_ITEM, items);

		this.setChanged();
		notifyObservers(notification);
	}
	
	
	/**
	 * Undo the adding of List of Items to the HIT
	 * @param items the List of Items to remove
	 * @throws IllegalArgumentException thrown if any of the Items could not be removed
	 * */
	public void UndoAddItems(List<Item> items) {
		for (Item item: items) {
			if (!itemIndex.CanRemoveItem(item))
				throw new IllegalArgumentException("Item cannot be removed");
			itemIndex.UndoAddItem(item);
		}
		
		//notify the observers
		ItemNotification notification = new ItemNotification(
				ItemNotification.Type.REMOVE_ITEM, items);

		this.setChanged();
		notifyObservers(notification);
	}
			
	/**
	 * Transfers an item from its current {@link ProductContainer} to another.
	 * via Drag Transfer
	 * @param item The {@link Item} to transfer.
	 * @param destination The destination ProductContainer.
	 * @return the transferred Item
	 * @throws IllegalArgumentException
	 */
	public List<Item> TransferItem(Item item, ProductContainer destination) 
												throws IllegalArgumentException{
		/*if (!itemIndex.CanTransferItem(item, item.GetContainer(), destination))
			throw new IllegalArgumentException("Cannot transfer item");*/
		
		List<Item> transferredItems = itemIndex.TransferItem(item, destination);
		
		//create a notification for the observers
		ItemNotification notification = new ItemNotification(ItemNotification.Type.TRANSFER_ITEM,
															transferredItems);
		this.setChanged();
		notifyObservers(notification);
		
		return transferredItems;
	}
	
	/**
	 * Transfer a List of Items from their current ProductContainer to a destination
	 * @param items the Items to transfer
	 * @param destination the ProductContainer to transfer to
	 * */
	public void TransferItems(List<Item> items, ProductContainer destination) {
		for (Item item: items)
			itemIndex.TransferItem(item, destination);
		
		//create a notification for the observers
		ItemNotification notification = new ItemNotification(ItemNotification.Type.TRANSFER_ITEM,
															items);
		this.setChanged();
		notifyObservers(notification);
	}
	
	//via TransferItemBatchView
	public Item TransferItemToTree(Item item, StorageUnit unit) throws IllegalArgumentException {
		
		itemIndex.TransferItemToTree(item, unit);
		
		List<Item> transferredItem = new ArrayList<Item>();
		transferredItem.add(item);
		ItemNotification notification = new ItemNotification(ItemNotification.Type.TRANSFER_ITEM,
															transferredItem);
		this.setChanged();
		notifyObservers(notification);
		
		return item;
	}
	
	public Iterator<Item> GetItemsByProduct(ProductContainer container, Product product){
		
		return itemIndex.GetItemsByProduct(container, product);
	}
}
